<!--
 * @Description: 路由判断前进还是后退/缓存/过度动效
 * @Author bianpengfei
 * @create 2021/12/27 20:58
 -->
<!--default-->
<script>
export default { name: 'g-router-view-cache' }
</script>

<!--setup-->
<script setup>
import { computed, defineEmits, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Type } from '@bianpengfei/utils'
import { generateNavAttr } from '~libs/utils/nav'
import setting from '/src/setting'

const props = defineProps({
  // 是否开启路由过度动画
  enableTransition: {
    type: Boolean,
    default: setting.routeTransition
  },
  // 缓存的最大值
  max: {
    type: Number,
    default: 99
  },
  // 包括name
  include: {
    type: [Array, String],
    default: () => []
  },
  // 排除缓存name
  exclude: {
    type: [Array, String],
    default: () => []
  }
})

const emit = defineEmits(['route-change'])

const route = useRoute()
const router = useRouter()

const routerViewRef = ref(null)
const keepAliveRef = ref(null)
const state = reactive({
  isReload: true,
  transitionName: 'fade',
  cacheRoutes: [],
  routingStack: [], // 路由堆栈
  navArr: []
})

let _routeDirection = '' // 路由方向
let _routingStackHistory = [] // 路由堆栈历史

const cacheRoutes_ = computed(() => {
  if (!setting.pageCache) return []
  return [
    ...new Set([
      ...(router?.getRoutes() || [])
        .filter(v => v?.components?.name && v?.meta?.permanentCache === true)
        .map(v => v?.components?.name),
      ...state.routingStack.reduce((pre, cur) => {
        if (Array.isArray(cur)) {
          cur.forEach(v => {
            if (v.name) pre.push(v.name)
          })
        } else if (Type.isObject(cur)) {
          if (cur.name && cur?.meta?.cache !== false) pre.push(cur.name)
        }
        return pre
      }, []),
      ...props.include
    ])
  ]
})

const transitionName_ = computed(() => props.enableTransition && state.transitionName)

/**
 * @vue-docs-ref
 * @description 重新加载
 * @param {Object} curRoute 当前路由实例
 * @return
 */
const reload = (curRoute = route) => {
  if (route?.meta?.cache !== false) {
    rmCacheRoute(curRoute.fullPath)
  }
  state.isReload = false
  setTimeout(() => {
    nextTick(() => {
      state.isReload = true
    })
  }, 20)
}

/**
 * 根据fullPath， 删除缓存中的路由
 * @param key
 */
const rmCacheRoute = (key = route.fullPath) => {
  // 删除组件在keepalive里的cache，参考 pruneCacheEntry 这个方法的逻辑
  // fix: https://github.com/vuejs/vue-next/issues/2077
  const keepAliveInstance = keepAliveRef.value.$
  const cache = keepAliveInstance.__v_cache
  const vnode = cache.get(key)
  if (vnode) {
    let shapeFlag = vnode.shapeFlag
    if (shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
      shapeFlag -= 256 /* COMPONENT_SHOULD_KEEP_ALIVE */
    }
    if (shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
      shapeFlag -= 512 /* COMPONENT_KEPT_ALIVE */
    }
    vnode.shapeFlag = shapeFlag
    const renderer = keepAliveInstance.ctx.renderer
    renderer.um(vnode, keepAliveInstance, keepAliveInstance.suspense)
    cache.delete(key)
  }
}

/**
 * @description 添加路由堆栈
 * @param {Object} data 当前路由实例
 * @return
 */
const pushRoutingStack = data => {
  const { routingStack } = toRefs(state)
  const idx = routingStack.value.findIndex(v => v.fullPath == data.fullPath)
  let routeStackNew = routingStack.value.slice(0)
  if (idx >= 0) {
    routeStackNew = routingStack.value.slice(0, idx + 1)
  } else {
    routeStackNew.push(data)
  }
  routingStack.value = routeStackNew
}

onMounted(() => {
  watch(
    () => state.routingStack,
    (newVal, oldVal) => {
      if (newVal.length < oldVal.length) {
        const removeView = oldVal.find(o => {
          return newVal.findIndex(i => i.fullPath === o.fullPath) < 0
        })
        if (removeView && removeView.meta?.cache !== false) {
          nextTick(() => {
            rmCacheRoute(removeView.fullPath)
          })
        }
      }
    }
  )

  /**
   * 监听路由变化
   */
  watch(
    () => route.fullPath,
    (newVal, oldVal) => {
      // 2、设置路由 前进还是后退
      if (newVal == '/' || !oldVal) {
        // 第一次进来
        _routeDirection = ''
        state.transitionName = 'fade'
      } else {
        const isExist = state.routingStack.some(v => v?.fullPath == newVal)
        if (isExist) {
          // 已存在
          _routeDirection = 'backward' // 后退
          state.transitionName = 'pop-out'
        } else {
          // 不存在
          state.transitionName = 'pop-in'
          _routeDirection = 'forward' // 前进
        }
      }
      const navAttr = generateNavAttr(route)
      _routingStackHistory.push(navAttr)
      pushRoutingStack(navAttr)
      emit('route-change', {
        routeDirection: _routeDirection, // 方向
        toFullPath: newVal,
        fromFullPath: oldVal,
        route,
        routingStack: state.routingStack,
        routingStackHistory: _routingStackHistory
      })
    },
    { immediate: true }
  )
})

// @expose
defineExpose({
  reload, // 重新加载路由
  rmCacheRoute // 删除缓存路由
})
</script>

<!--render-->
<template>
  <div class="router-view__wrap">
    <router-view v-slot="{ Component }" ref="routerViewRef">
      <transition :name="transitionName_" appear>
        <keep-alive :include="cacheRoutes_" :exclude="props.exclude" :max="props.max" ref="keepAliveRef">
          <component v-if="state.isReload" :is="Component" :key="$route.fullPath" class="router-view" />
        </keep-alive>
      </transition>
    </router-view>
  </div>
</template>

<!--style-->
<style scoped lang="less">
.router-view__wrap {
  .div--webkit--scrollbar();
  flex: 1;
  width: 100%;
  height: 100%;
  .router-view {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
  }

  .pop-out-enter-active,
  .pop-out-leave-active,
  .pop-in-enter-active,
  .pop-in-leave-active {
    will-change: transform, opcity;
    height: 100%;
    width: 100%;
    flex: 1;
    position: absolute;
    backface-visibility: hidden;
    perspective: 1000;
    -webkit-backface-visibility: hidden;
    -webkit-perspective: 1000;
    content-visibility: auto;
    transition: transform 0.3s ease, opacity 0.3s ease, visibility 0.3s ease;
    box-shadow: -0.1rem 0 0.12rem rgba(0, 0, 0, 0.1);
  }

  /*路由前进，进入*/
  .pop-in-enter-from {
    opacity: 0;
    transform: translate3d(90%, 0, 0);
  }
  .pop-in-leave-to {
    opacity: 0;
    transform: translate3d(-90%, 0, 0);
  }

  /*路由退出，后退*/
  .pop-out-enter-from {
    will-change: transform, opcity;
    transform: translate3d(-100%, 0, 0);
  }
  .pop-out-leave-to {
    opacity: 0;
    transform: translate3d(100%, 0, 0);
  }

  /* 渐变 */
  .fade-enter-active,
  .fade-leave-active {
    transition: opacity 0.28s;
  }
  .fade-enter-from,
  .fade-leave-active {
    opacity: 0;
  }
}
</style>
